/**
 * @author Tobias Boehm
 *
 * 06.06.2011
 */
package org.anddev.andengine.extension.tbsengine.match;

import java.io.IOException;
import java.util.UUID;
import java.util.Vector;

import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.entity.layer.tiled.tmx.util.exception.TMXLoadException;
import org.anddev.andengine.extension.tbsengine.map.AbsMap;
import org.anddev.andengine.extension.tbsengine.map.AbsMapLoader;
import org.anddev.andengine.extension.tbsengine.match.gamestate.AbsClientGlobalGameState;
import org.anddev.andengine.extension.tbsengine.match.gamestate.AbsGlobalGameState;
import org.anddev.andengine.extension.tbsengine.match.gamestate.AbsPlayerGameState;
import org.anddev.andengine.extension.tbsengine.match.gamestate.AbsServerGlobalGameState;
import org.anddev.andengine.util.path.Path.Step;
import org.xml.sax.SAXException;

import android.content.Context;

/**
 * This is an abstract representation of a match. It contains all necessary 
 * information to represent a running match including its game state.
 * <p>
 * Notice:<br>
 * You may not implement this class but its further developed children 
 * {@link AbsClientMatch} and {@link AbsServerMatch}.
 * 
 * @author Tobias Boehm
 *
 * @param <PlayerGameStateClass> - Your implementation of {@link AbsPlayerGameState}
 * @param <GlobalGameStateClass> - Your implementation of {@link AbsGlobalGameState}
 * @param <MapClass> - Your Implementation of {@link AbsMap}
 */
public abstract class AbsMatch<PlayerGameStateClass extends AbsPlayerGameState,
							   GlobalGameStateClass extends AbsGlobalGameState,
							   MapClass extends AbsMap> {
									

	/**
	 * A match could be in the states described in this enum.
	 * 
	 * @author Tobias Boehm
	 *
	 */
	public static enum MatchState{
	
		/**
		 * The match is not yet prepared. You shouldn't do anything with this 
		 * match until you have called {@link AbsMatch#prepareMatch()}
		 */
		UNKOWN,
		
		/**
		 * The match is looking for players. It won't start the first round 
		 * until all players have reconnect to the server.
		 */
		LOOKING_FOR_PLAYERS,
		
		/**
		 * The match is running.
		 */
		RUNNING,
		
		/**
		 * The match is completed. It is not allowed to change any
		 * {@link Round}s, {@link Turn}s or {@link Step}s or to rejoin this match.
		 */
		COMPLETED
	};
	
	/**
	 * The unique identifier of this match.
	 */
	protected final UUID mMatchID;
	
	/**
	 * The global game state of this match (it contains all
	 * {@link AbsPlayerGameState}s).
	 */
	protected GlobalGameStateClass mGlobalGameState;
	
	/**
	 * The map on witch this match gets played.
	 */
	protected final MapClass mMap;
	
	/**
	 * A vector of all completed rounds. A {@link Round} can only called 
	 * completed if it is validated by the server.
	 */
	protected final Vector<Round<PlayerGameStateClass>> mCompletedRounds = new Vector<Round<PlayerGameStateClass>>();
	
	/**
	 * The running round.
	 */
	protected Round<PlayerGameStateClass> mCurrentRound;
	
	/**
	 * The state of the match.
	 * 
	 * @see MatchState
	 */
	protected MatchState mMatchState = MatchState.UNKOWN;
	
	protected final Context mContext;
	
	/**
	 * A reference to the game engine.
	 */
	protected final Engine mEngine;

	
	/**
	 * This is the default constructor.
	 *  
	 * @param pContext
	 * @param pEngine - A reference to the game engine.
	 * @param pMatchID - The ID of this match.
	 * @param pMapID - The ID of the map which will be played.
	 * 
	 * @throws IOException This exception is thrown if {@link #loadMap(String, Context, Engine)} is unable to load the map file.
	 * @throws SAXException This exception is thrown if {@link #loadMap(String, Context, Engine)} is unable to parse the map file.
	 * @throws TMXLoadException This exception is thrown if {@link #loadMap(String, Context, Engine)} discovers a problem with the map file format.
	 */
	public AbsMatch(final Context pContext, final Engine pEngine ,final UUID pMatchID, final String pMapID) throws IOException, SAXException, TMXLoadException{
		
		this.mContext = pContext;
		
		this.mEngine = pEngine;
		
		this.mMatchID = pMatchID;
			
		this.mMap = this.loadMap(pMapID, pContext, pEngine);
	}

	/**
	 * This method should load the map which could be identified by the given 
	 * <code>pMapID</code>. You may want to create an instance of your implement
	 * of {@link AbsMapLoader} to do this job.
	 *  
	 * @param pMapID - The ID of the wanted map.
	 * @param pContext
	 * @param pEngine - A reference to the game engine.
	 * 
	 * @return An instance of your implementation of {@link AbsMap} including all information related to the map with the given ID.
	 * 
	 * @throws IOException This exception is thrown if {@link #loadMap(String, Context, Engine)} is unable to load the map file.
	 * @throws SAXException This exception is thrown if {@link #loadMap(String, Context, Engine)} is unable to parse the map file.
	 * @throws TMXLoadException This exception is thrown if {@link #loadMap(String, Context, Engine)} discovers a problem with the map file format.
	 */
	protected abstract MapClass loadMap(final String pMapID, final Context pContext, final Engine pEngine) throws IOException, SAXException, TMXLoadException;

	/**
	 * This method should return an instance of your implementation of
	 * {@link AbsClientGlobalGameState} or {@link AbsServerGlobalGameState}.
	 * 
	 * @return An instance of your implementation of {@link AbsClientGlobalGameState} or {@link AbsServerGlobalGameState}.
	 */
	protected abstract GlobalGameStateClass createGlobalGameState();
	
	/**
	 * In this method you should do all necessary steps to complete this match.
	 */
	public abstract void completeMatch();

	/**
	 * This method has to be called at the method body end of every children 
	 * constructor of this class. This is necessary to make sure that all member
	 * variables has been set before methods get called which may need those
	 * member variables.
	 */
	protected void initMatch(){
		
		this.mGlobalGameState = this.createGlobalGameState();
		
		this.mMatchState = MatchState.LOOKING_FOR_PLAYERS;
	}
		
	/**
	 * @return the mCurrentRound
	 */
	public Round<PlayerGameStateClass> getCurrentRound() {
		return mCurrentRound;
	}

	/**
	 * @param mCurrentRound the mCurrentRound to set
	 */
	public void setCurrentRound(Round<PlayerGameStateClass> mCurrentRound) {
		this.mCurrentRound = mCurrentRound;
	}

	/**
	 * @return the mMap
	 */
	public MapClass getMap() {
		return mMap;
	}

	/**
	 * @return the mCompletedRounds
	 */
	public Vector<Round<PlayerGameStateClass>> getCompletedRounds() {
		return mCompletedRounds;
	}
	
	public void moveCurrendRoundToCompletedRounds(){
			
		this.mCompletedRounds.add(this.mCurrentRound);
		
		this.mCurrentRound = null;
	}

	/**
	 * @return the mMatchID
	 */
	public UUID getMatchID() {
		return mMatchID;
	}

	/**
	 * @return the mContext
	 */
	public Context getContext() {
		return mContext;
	}

	/**
	 * @return the mGlobalGameState
	 */
	public GlobalGameStateClass getGlobalGameState() {
		return mGlobalGameState;
	}
}
